﻿using System;
using System.Collections.Generic;
using System.Linq;
using CI.Interfaces;
using CI.Organism;


namespace CI.Mesopotamia
{
    /// <summary>
    /// Fitness function that evaluates and places a high score on travelling over new area and reduces on bumper hit detection on organisms
    /// Maximum New Travelled Area Bumper Hit Detection
    /// </summary>
    public class MNTABHD : FitnessFunction, IFitnessLevelEvaluator
    {
        private readonly Dictionary<IOrganism, List<ILocation>> _organismLocations = new Dictionary<IOrganism, List<ILocation>>();
        protected readonly Dictionary<Guid, int> OrganismMovementCount = new Dictionary<Guid, int>();
        public const decimal NewLocationMultiplier = 0.8m;
        private readonly SimEnv _simEnv = SimEnv.Instance;
        private const int MinFitEnoughMovementCount = 10;

        /// <summary>
        /// Evaluate the orgainsms and update in...
        /// note: only alive organisms can increase from moving
        /// 
        /// If the organism is not dirty then it has not moved and loses energy
        /// If it is dirty and this is new location then give it more energy
        /// </summary>
        /// <param name="organisms"></param>
        public override void UpdateOrganisms(IOrganismCollection organisms)
        {
            #region Argument Validators
            if (organisms == null)
                throw new ArgumentException("organisms");
            #endregion

            organisms.DirtyLocationsRebuild();

            // test each organism
            // todo : refactor for parallel.ForEach
            foreach (IOrganism organism in organisms.DirtyLocations)
            {
                if (!organisms.Alive.Contains(organism))
                    continue;

                UpdateStrength(organism);
            }

            // purge the organism locations of unused organisms
            RemoveNonAliveOrganisms(organisms);
        }

        /// <summary>
        /// Remove non alive organisms from the list
        /// </summary>
        /// <param name="organisms"></param>
        private void RemoveNonAliveOrganisms(IOrganismCollection organisms)
        {
            foreach (var organism in new Dictionary<IOrganism, List<ILocation>>(_organismLocations))
            {
                // if not alive then dont check it
                if (!organisms.Alive.Contains<IOrganism>(organism.Key))
                {
                    _organismLocations.Remove(organism.Key);
                }
            }
        }

        /// <summary>
        /// if they have moved then give them strength, they have to have moved by a full body length else it doesnt count(eg check radius)
        /// the base fitness function will take 1 off so add 2, and if they havent moved they get that 1 deduction
        /// todo : update this to see if they have covered new area else they dont get any more energy
        /// </summary>
        /// <param name="organism"></param>
        private void IncreaseOnMovement(IOrganism organism)
        {
            if (_organismLocations.ContainsKey(organism))
            {
                if (NoOverlap(organism))
                {
                    NewLocationSaved(organism);
                }
            }
            else
            {
                var locations = new List<ILocation> { organism.Location };
                _organismLocations.Add(organism, locations);
                NewLocationSaved(organism);

            }
        }

        private void NewLocationSaved(IOrganism organism)
        {
            // add the current location for later comparison
            organism.Energy += Convert.ToInt64(organism.CIET * NewLocationMultiplier);
            _organismLocations[organism].Add(organism.Location);

            if (!OrganismMovementCount.ContainsKey(organism.Guid))
                OrganismMovementCount[organism.Guid] = 0;

            OrganismMovementCount[organism.Guid]++;
        }

        /// <summary>
        /// check to see if this organisms current location doesnt have any overlap with pervious locations taking into consideration its radius
        /// </summary>
        /// <param name="organism"></param>
        /// <returns></returns>
        private bool NoOverlap(IOrganism organism)
        {
            ILocation orgLocation = new Location(organism.Location) { XRadius = 0, YRadius = 0, ZRadius = 0 };

            foreach (ILocation location in _organismLocations[organism])
            {
                if (LocationCollisions.LocationsCollide(location, orgLocation))
                    return false;
            }

            return true;
        }

        /// <summary>
        /// if the bumper or a collison has occured then take strength off
        /// search through the processed list in the organisms
        /// </summary>
        /// <param name="organism"></param>
        private void DecreaseOnCollisions(IOrganism organism)
        {
            if (organism.NeuralSystem != null)
            {
                // check each neuron
                foreach (INeuron neuron in new NeuronCollection(organism.NeuralSystem.ProcessedNeuronList))
                {
                    // check each input synapse
                    foreach (ISynapse synapse in neuron.InputSynapse)
                    {
                        if (!(synapse.SourceNotification is SynapseIO))
                            continue;

                        var syapseIO = synapse.SourceNotification as SynapseIO;

                        // if we have a output value and the synapse binding name of bumper then do a energy reduction
                        if ((syapseIO.CurrentDisplayValue > 0) && (syapseIO.BindingName.ToLower().Contains("bumper")))
                        {
                            _simEnv.TraceLogger.LogDebug(string.Format("Input SynapseIO with bumper fired and reducing Energy {0}", syapseIO.BindingName));
                            organism.Energy -= organism.CIET;
                        }
                    }
                }
            }
        }


        public void UpdateStrength(IOrganism organism)
        {
            IncreaseOnMovement(organism);
            DecreaseOnCollisions(organism);
        }

        public bool UpgradeToNextLevel(IOrganism organism)
        {
            return false;
            // todo : implement
        }

        public void RemoveOrganism(IOrganism organism)
        {
            _organismLocations.Remove(organism);
        }

        public override long EvaluateFitnessAndUpdateInternalFitnessValue(IOrganism organism)
        {
            var baseFitness = base.EvaluateFitnessAndUpdateInternalFitnessValue(organism);
            return baseFitness <= 0 ? baseFitness : CalcFitnessBasedOnOrganismMovement(organism, baseFitness);
        }

        private long CalcFitnessBasedOnOrganismMovement(IOrganism organism, long baseFitness)
        {
            int movementCount;
            if (!OrganismMovementCount.TryGetValue(organism.Guid, out movementCount))
                baseFitness = 0;
            else
            {
                if (movementCount < MinFitEnoughMovementCount)
                    baseFitness = 0;
            }

            return baseFitness;
        }


        public string ToString(IOrganism organism)
        {
            return base.ToString();
        }
    }
}
